package com.mydemo.likedy.schedul;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mydemo.likedy.Constant.AuditStatus;
import com.mydemo.likedy.Constant.RedisConstant;
import com.mydemo.likedy.Service.SettingService;
import com.mydemo.likedy.Service.video.VideoService;
import com.mydemo.likedy.Utils.RedisCacheUtil;
import com.mydemo.likedy.config.RabbitMQConfig;
import com.mydemo.likedy.pojo.entity.HotRankBatchMessage;
import com.mydemo.likedy.pojo.entity.HotRankEndMessage;
import com.mydemo.likedy.pojo.entity.Setting;
import com.mydemo.likedy.pojo.entity.video.Video;
import com.mydemo.likedy.pojo.vo.HotVideo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 热度排行榜
 */
@Component
@Slf4j
public class HotRank {

    @Autowired
    private VideoService videoService;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private SettingService settingService;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
    ObjectMapper om = new ObjectMapper();

    {
        jackson2JsonRedisSerializer.setObjectMapper(om);
    }

    /**
     * 热度排行榜
     */
    @Scheduled(cron = "0 * * * * ?")
    // 生成热点排名
    public void generateHotRank() {
        String taskId = UUID.randomUUID().toString();
        long id = 0;
        int batchIndex = 0;
        final Date baseTime = new Date(); // 统一基准时间
        long limit = 1000;
        // 每次拿1000个
        //从数据库拿出所有视频记录
        while (true) {
            List<Video> videos = videoService.list(new LambdaQueryWrapper<Video>()
                    .select(Video::getId, Video::getShareCount, Video::getHistoryCount, Video::getStartCount, Video::getFavoritesCount,
                            Video::getGmtCreated, Video::getTitle).gt(Video::getId, id)
                    .eq(Video::getAuditStatus, AuditStatus.SUCCESS).eq(Video::getOpen, 0).last("limit " + limit));
            ;
            if (videos.isEmpty()) break;

            // 构建消息
            HotRankBatchMessage message = new HotRankBatchMessage();
            message.setTaskId(taskId);
            message.setBatchIndex(++batchIndex);
            message.setVideos(videos);
            message.setBaseTime(baseTime);

            // 发送到RabbitMQ
            rabbitTemplate.convertAndSend(
                    RabbitMQConfig.HOT_RANK_EXCHANGE,
                    RabbitMQConfig.HOT_RANK_ROUTING_KEY,
                    message,
                    m -> {
                        // 设置消息属性
                        m.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
                        m.getMessageProperties().setCorrelationId(taskId);
                        m.getMessageProperties().setHeader("taskId", taskId);

                        return m;
                    }
            );

            id = videos.get(videos.size() - 1).getId();
        }

        // 发送结束标志
        HotRankEndMessage endMessage = new HotRankEndMessage();
        endMessage.setTaskId(taskId);
        endMessage.setTotalBatches(batchIndex);

        rabbitTemplate.convertAndSend(
                RabbitMQConfig.HOT_RANK_EXCHANGE,
                RabbitMQConfig.HOT_RANK_END_ROUTING_KEY,
                endMessage,
                message ->
        {message.getMessageProperties().setHeader("taskId", taskId);return message;}
        );

        log.info("热点排名任务已启动: taskId={}, 总批次数={}", taskId, batchIndex);
    }

    // 热门视频,没有热度排行榜实时且重要
    @Scheduled(cron = "0 0 */3 * * ?")
    public void hotVideo() {
        // 分片查询3天内的视频
        int limit = 1000;
        long id = 1;
        List<Video> videos = videoService.selectNDaysAgeVideo(id, 3, limit);
        final Double hotLimit = settingService.list(new LambdaQueryWrapper<Setting>()).get(0).getHotLimit();
        Calendar calendar = Calendar.getInstance();
        int today = calendar.get(Calendar.DATE);

        while (!ObjectUtils.isEmpty(videos)) {
            final ArrayList<Long> hotVideos = new ArrayList<>();
            for (Video video : videos) {
                //计算每个视频的权重
                Long shareCount = video.getShareCount();
                Double historyCount = video.getHistoryCount() * 0.8;
                Long startCount = video.getStartCount();
                Double favoritesCount = video.getFavoritesCount() * 1.5;
                final Date date = new Date();
                long t = date.getTime() - video.getGmtCreated().getTime();
                final double hot = hot(shareCount + historyCount + startCount + favoritesCount, TimeUnit.MILLISECONDS.toDays(t));

                // 大于X热度说明是热门视频
                if (hot > hotLimit) {
                    hotVideos.add(video.getId());
                }

            }

            id = videos.get(videos.size() - 1).getId();
            videos = videoService.selectNDaysAgeVideo(id, 3, limit);
            // RedisConstant.HOT_VIDEO + 今日日期 作为key  达到元素过期效果
            if (!ObjectUtils.isEmpty(hotVideos)) {
                String key = RedisConstant.HOT_VIDEO + today;
                redisTemplate.opsForSet().add(key, hotVideos.toArray(new Object[hotVideos.size()]));
                redisTemplate.expire(key, 3, TimeUnit.DAYS);
            }

        }


    }

    static double a = 0.011;

    public static double hot(double weight, double t) {

        return weight * Math.exp(-a * t);
    }


    public double weightRandom() {
        int i = (int) ((Math.random() * 9 + 1) * 100000);
        return i / 1000000.0;
    }

}
